// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Xml;

using Tierless.Framework.Entities;
using Tierless.Framework.EntityTypes;

namespace Tierless.Framework.ServiceContracts
{
	public class ReferencePreservingDataContractFormatAttribute : Attribute, IOperationBehavior
	{
		public ReferencePreservingDataContractFormatAttribute()
		{}

		public ReferencePreservingDataContractFormatAttribute(bool reportEntityTypes)
		{
			_reportEntityTypes = reportEntityTypes;
		}

		private bool _reportEntityTypes;

		public bool ReportEntityTypes
		{
			get { return _reportEntityTypes; }
			set { _reportEntityTypes = value; }
		}

		#region IOperationBehavior Members

		public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
		{}

		public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
		{
			IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(description, _reportEntityTypes);
			innerBehavior.ApplyClientBehavior(description, proxy);
		}

		public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch)
		{
			IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(description, _reportEntityTypes);
			innerBehavior.ApplyDispatchBehavior(description, dispatch);
		}

		public void Validate(OperationDescription description)
		{}

		#endregion
	}

	public class ReferencePreservingDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
	{
		//See comments below
		//private static bool configurationRead = false;
		private static int _maxItemsInObjectGraph = int.MaxValue; //Default is Int16.MaxValue
		private static bool _preserveObjectReferences = true;

		private static object _lockHelper = new object();

		public ReferencePreservingDataContractSerializerOperationBehavior(OperationDescription operationDescription, bool reportEntityTypes)
			: base(operationDescription)
		{
			_reportEntityTypes = reportEntityTypes;
		}

		private bool _reportEntityTypes;

		public bool ReportEntityTypes
		{
			get { return _reportEntityTypes; }
			set { _reportEntityTypes = value; }
		}

		private IEnumerable<Type> GetKnownTypes(IList<Type> knownTypes)
		{
			if (_reportEntityTypes)
			{
				var entityTypes = NamedEntityTypeObject.GetKnownTypes();
				if (knownTypes == null)
					knownTypes = entityTypes;
				else
				{
					foreach (var type in entityTypes.Where(AType => !knownTypes.Contains(AType)))
					{
						knownTypes.Add(type);
					}
				}
			}

			return knownTypes;
		}

		public override XmlObjectSerializer CreateSerializer(Type type, string name, string namespaceValue, IList<Type> knownTypes)
		{
			return CreateDataContractSerializer(type, name, namespaceValue, GetKnownTypes(knownTypes));
		}

		public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString namespaceValue, IList<Type> knownTypes)
		{
			return CreateDataContractSerializer(type, name.Value, namespaceValue.Value, GetKnownTypes(knownTypes));
		}

		private static XmlObjectSerializer CreateDataContractSerializer(Type type, string name, string namespaceValue, IEnumerable<Type> knownTypes)
		{
			//ReadConfiguration(name);

			return 
				new DataContractSerializer
				(
					type,
					name,
					namespaceValue,
					knownTypes,
					_maxItemsInObjectGraph /*maxItemsInObjectGraph*/,
					false /*ignoreExtensionDataObject*/,
					_preserveObjectReferences /*preserveObjectReferences*/,
					null /*dataContractSurrogate*/
				);
		}
	}
}